#!/usr/bin/python3
# -*- coding: utf-8 -*-

from time import perf_counter
from typing import Any, Callable, Dict, List, Optional

from playwright.async_api import BrowserContext, ElementHandle, Frame, Page
from playwright.async_api._generated import FrameLocator, Locator

from components.dialog import Dialog
from components.event import Event
from components.expect import Assert
from components.keyboard import Keyboard
from components.message import Message
from components.mouse import Mouse
from components.popout import Popout
from components.request import Request
from components.select import Select
from components.table import Table
from components.toolbar import Toolbar
from utils.cache_util import cache_manager
from utils.log import log
from utils.mixin_util import MixinUtil


class BasePage(MixinUtil):
    def __init__(self, page: Page):
        self.page = page
        self.debug = False

        self.dialog = Dialog(page)
        self.keyboard = Keyboard(page)
        self.mouse = Mouse(page)
        self.popout = Popout(page)
        self.select = Select(page)
        self.table = Table(page)
        self.toolbar = Toolbar(page)

        self.expect = Assert(page)
        self.request = Request(page)
        self.event = Event(page)
        self.message = Message(page)

        self.navigate_timeout = 20000  # 导航全局超时时间
        self.wait_response_timeout = 10000  # 等待响应的时间
        self.api_timeout = 30000  # api timeout
        self.delay = 10  # 默认全局等待时间
        self.type_delay = 10  # type input 全局输入延迟
        self.env = cache_manager.get('env')
        self.region = cache_manager.get('region')
        self.zone = cache_manager.get('zone')
        self.login_url = cache_manager.get('login_url')
        self.base_url = cache_manager.get('base_url')
        self.console_url = cache_manager.get('console_url')
        self.route = ""  # 主页面路由
        self.sub_route = ""  # 子路由

    def navigate(
            self, region: str = '', route: str = '', other_url: str = None
    ) -> None:
        """根据路region和路由、或者url来导航"""
        if other_url is not None:
            target_url = other_url
        else:
            if region is None:
                region = self.region
            target_url = f"{self.base_url}/{region}/{route}"
        start = perf_counter()
        try:
            self.page.goto(
                url=target_url, timeout=self.navigate_timeout, wait_until="load"
            )
            elapsed_time = perf_counter() - start
            log.info(f"导航到:{target_url} 加载耗时: {elapsed_time:.2f} s")
        except Exception as e:
            log.error(f"导航到:{target_url} 异常,原因:{e}")
            raise e

    def locator(self, selector: str) -> Locator:
        """
        元素定位器
        :param selector:
        :return:
        """
        try:
            locator = self.page.locator(selector)
            log.debug(f"定位器: {locator}")
            return locator
        except Exception as e:
            log.error(f"locator定位器: {selector} 异常,原因:{e}")
            raise e

    def frame_locator(self, selector: str) -> FrameLocator:
        """
        Frame定位器
        :param selector:
        :return:
        """
        try:
            frame_locator = self.page.frame_locator(selector)
            log.debug(f"Frame定位器: {frame_locator}")
            return frame_locator
        except Exception as e:
            log.error(f"Frame定位器: {selector} 异常,原因:{e}")
            raise e

    def screenshot(self, path: str, full_page: bool = True, locator: str = None):
        """
        截图功能，默认截取全屏，如果传入定位器表示截取元素
        :param path: 截图路径
        :param full_page: 截长图
        :param locator: 元素
        :return:
        """
        if locator is not None:
            self.locator(locator).screenshot(path=path)
            return path
        self.page.screenshot(path=path, full_page=full_page)
        return path

    def get_cookies(self, url: str = None) -> Dict[str, str]:
        """
        获取cookies
        :param url:urls，则只返回影响这些 url 的cookie
        :return:
        """
        if url:
            cookies = self.page.context.cookies(url)
        else:
            cookies = self.page.context.cookies()
        log.info(f"获取到的cookies为:{cookies}")
        return cookies

    def add_cookie(self, cookie: dict) -> None:
        """
        添加 Cookies 到当前页面。
        """
        try:
            self.page.context.add_cookies(cookie)
        except Exception as e:
            log.error(f"添加 Cookies 时发生错误：{e}")

    def del_cookies(self) -> None:
        """
        删除当前页面的所有 Cookies
        """
        try:
            self.page.context.clear_cookies()
        except Exception as e:
            log.error(f"删除 Cookies 时发生错误：{e}")

    def wait_for_load_state(self, state: str = "networkidle") -> None:
        """
        等待页面加载完成
        :param state: domcontentloaded/load/networkidle
        :return:
        """
        self.page.wait_for_load_state(state=state)

    def browser_operation(
            self, reload: bool = False, forward: bool = False, back: bool = False
    ) -> None:
        """
        浏览器操作
        :param reload: 刷新
        :param forward: 前进
        :param back: 后退
        :return:
        """
        if reload:
            self.page.reload()
        if back:
            self.page.go_back()
        if forward:
            self.page.go_forward()

    def get_content(self) -> str:
        """
        获取当前页面的内容。
        Returns:
            str: 当前页面的内容。
        """
        try:
            return self.page.content()
        except Exception as e:
            log.error(f"获取页面内容时发生错误：{e}")
            return ""

    def switch_to_page(
            self, context: BrowserContext, url: str = None, title: str = None
    ) -> Page:
        """
        切换到指定url
        :param context:传入一个浏览器上下文
        :param title: 当前标签页的标题
        :param url: 当前标签页的url
        :return: label_page:Page对象 返回对应的标签页,如果没找到则返回最新的标签页
        """
        for label_page in context.pages:
            if url:
                if url in label_page.url:
                    label_page.bring_to_front()
                    return label_page
            elif title:
                if title in label_page.title():
                    label_page.bring_to_front()
                    return label_page
        else:
            if title:
                log.info(f"没有找到【{title}】标题的标签页")
            if url:
                log.info(f"没有找到【{url}】网址的标签页")
        return context.pages[-1]

    def evaluate(self, js: str) -> None:
        """
        执行js
        :param js: 需要执行的js
        :return:
        """
        try:
            self.page.evaluate(js)
        except Exception as e:
            log.error(f"执行js时发生错误：{e}")

    def click(self, locator, frame_locator=None):
        """
        hover到元素并点击元素
        :param locator: 传入元素定位器
        :param frame_locator: 传入frame框架的定位器，如果没有传入一般为点击
        :return:
        """
        try:
            if frame_locator is not None:
                self.frame_locator(frame_locator).locator(locator).hover()
                self.frame_locator(frame_locator).locator(locator).click()
            else:
                self.page.hover(locator)
                self.page.click(locator)
        except Exception as e:
            log.error(f"点击元素时异常:{e}")
            raise e

    def fill(self, locator, text: [str, int] = "", is_type: bool = False) -> None:
        """
        输入文本
        :param locator: 定位器
        :param text: 输入的文本内容
        :param is_type: 是否模拟手动输入
        :return:
        """
        try:
            locator = self.locator(locator)
            locator.hover()
            if is_type:
                locator.type(text, delay=self.type_delay)
            else:
                locator.fill(text)
                assert self.get_text(locator, input_value=True) == text
        except Exception as e:
            log.error(f"输入文本时异常:{e}")
            raise e

    def get_text(self, locator, attr=False, inner_text=False, input_value=False) -> str:
        """获取元素属性值"""
        if attr:
            return locator.get_attribute(attr)
        if inner_text:
            return locator.inner_text()
        if input_value:
            return locator.input_value()

    def ele_to_be_visible_force(self, locator, frame_locator=None, timeout: int = 5):
        """强制等待某个元素可见"""
        ele = None
        if frame_locator is not None:
            ele = self.frame_locator(frame_locator).locator(locator)
        else:
            ele = self.locator(locator)
            for t in range(0, timeout):
                self.page.wait_for_timeout(500)
                if ele.is_visible():
                    break
            else:
                raise Exception("元素未找到!")

    def wait_until_element(self, selector: str, timeout: Optional[int] = None):
        """
        等待特定元素出现在页面上。
        Args:
            selector (str): 元素的选择器。
            timeout (int, optional): 最大等待时间，以毫秒为单位。默认为None，使用Playwright的默认超时时间。
        Raises:
            TimeoutError: 如果超时未找到指定的元素。
        """
        try:
            self.page.wait_for_selector(selector, timeout=(timeout * 1000))
        except Exception as e:
            raise TimeoutError(f"等待元素 {selector} 出现时发生超时错误：{e}")

    def wait_for_timeout(self, timeout: int) -> None:
        """
        等待指定的时间（毫秒）。
        Args:
            timeout (int): 等待的时间，以毫秒为单位。
        """
        try:
            log.info(f"固定等待:{timeout}")
            self.page.wait_for_timeout(timeout)
        except Exception as e:
            log.error(f"等待时间时发生错误：{e}")

    def find_elements(self, selector: str, selector_type=None) -> List[ElementHandle]:
        """
        查找匹配指定选择器的所有元素。

        Args:
            selector (str): 元素的选择器。
            selector_type (str): 选择器类型。
        Returns:
            List[ElementHandle]: 匹配的所有元素的列表。
        """
        try:
            elements = self.page.query_selector_all(selector)
            return elements
        except Exception as e:
            log.error(f"查找元素时发生错误：{e}")
            return []

    def find_element(
            self, selector: str, selector_type=None
    ) -> Optional[ElementHandle]:
        """
        查找匹配指定选择器的第一个元素。

        Args:
            selector (str): 元素的选择器。
            selector_type (str): 选择器类型。

        Returns:
            Optional[ElementHandle]: 匹配的第一个元素的 ElementHandle 对象，如果找不到则返回 None。
        """
        try:
            element = self.page.query_selector(selector)
            return element
        except Exception as e:
            log.error(f"查找元素时发生错误：{e}")
            return None

    def find_iframe_elements(self, selector: str, iframe: Frame) -> List[ElementHandle]:
        """
        在指定的 iframe 中查找匹配选择器的所有元素。

        Args:
            selector (str): 元素的选择器。
            iframe (Frame): 要在其中查找元素的 iframe。

        Returns:
            list[ElementHandle]: 匹配的所有元素的 ElementHandle 对象列表。
        """
        try:
            elements = iframe.query_selector_all(selector)
            return elements
        except Exception as e:
            log.error(f"在 iframe 中查找元素时发生错误：{e}")
            return []

    def find_iframe_element(self, selector: str, iframe: Frame) -> Any:
        """
        在指定的 iframe 中查找匹配选择器的第一个元素。

        Args:
            selector (str): 元素的选择器。
            iframe (Frame): 要在其中查找元素的 iframe。

        Returns:
            ElementHandle: 匹配的第一个元素的 ElementHandle 对象，如果找不到则返回 None。
        """
        try:
            element = iframe.query_selector(selector)
            return element
        except Exception as e:
            log.error(f"在 iframe 中查找元素时发生错误：{e}")
            return None

    @staticmethod
    def api_action(api_action: str) -> Callable:
        """
        返回当前发起请求的url后缀action
        :param api_action: 需要执行的api
        :return: 例如: action=CreateRouters
        """
        log.info(f"正在截取Api:{api_action} 的数据")
        return lambda response: response.url.endswith(f'action={api_action}')
